﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;

namespace AndersLiu.PE
{
    // CorHdr.h: CorElementType
    public enum ElementType : byte
    {
        End = 0x00,
        Void = 0x01,
        Boolean = 0x02,
        Char = 0x03,
        I1 = 0x04,
        U1 = 0x05,
        I2 = 0x06,
        U2 = 0x07,
        I4 = 0x08,
        U4 = 0x09,
        I8 = 0x0A,
        U8 = 0x0B,
        R4 = 0x0C,
        R8 = 0x0D,
        String = 0x0E,

        Ptr = 0x0F,
        ByRef = 0x10,

        ValueType = 0x11,
        Class = 0x12,
        Var = 0x13,
        Array = 0x14,
        GenericInst = 0x15,
        TypedByRef = 0x16,

        I = 0x18,
        U = 0x19,
        FnPtr = 0x1B,
        Object = 0x1C,
        SzArray = 0x1D,

        MVar = 0x1E,

        CModReqd = 0x1F,
        CModOpt = 0x20,
        Internal = 0x21,

        Modifier = 0x40,
        Sentinel = 0x01 | Modifier,
        Pinned = 0x05 | Modifier,
        R4HFA = 0x06 | Modifier,
        R8HFA = 0x07 | Modifier,
    }

    // CorHdr.h: CorCallingConvention
    // CorHdr.h: CorUnmanagedCallingConvention (1~4)
    [Flags]
    public enum CallConv : byte
    {
        Default = 0x00,

        UnmanagedC = 0x01,
        UnmanagedStdCall = 0x02,
        UnmanagedThisCall = 0x03,
        UnmanagedFastCall = 0x04,

        VarArg = 0x05,
        Field = 0x06,
        LocalSig = 0x07,
        Property = 0x08,
        Unmanaged = 0x09,
        GenericInst = 0x0A,
        NativeVarArg = 0x0B,

        Mask = 0x0F,
        Generic = 0x10,
        HasThis = 0x20,
        ExplicitThis = 0x40,
    }

    public static class CallConvUtility
    {
        public static CallConv RemoveMask(
            CallConv callConv)
        {
            return callConv & CallConv.Mask;
        }

        public static bool IsGeneric(CallConv callConv)
        {
            return (callConv & CallConv.Generic) != 0;
        }

        public static bool HasThis(CallConv callConv)
        {
            return (callConv & CallConv.HasThis) != 0;
        }

        public static bool IsExplicitThis(CallConv callConv)
        {
            return (callConv & CallConv.ExplicitThis) != 0;
        }
    }

    /// <summary>
    /// Indicates method signature type, such as
    /// MethodDef, MethodRef, StandAloneMethod and so on.
    /// </summary>
    public enum MethodSignatureType
    {
        Unspecified,
        MethodDef,
        MethodRef,
        StandAloneMethod,
    }

    public abstract class MetadataSignature
    {
        public int Index { get; internal set; }
        public int Length { get; internal set; }

        internal MetadataSignature() { }

        public static MethodSignature MatchMethodSignature(byte[] blob)
        {
            if (blob == null)
                throw new ArgumentNullException("blob");

            var index = 0;
            var sig = MatchMethodSignature(blob, ref index);

            Debug.Assert(index == blob.Length);
            return sig;
        }

        public static PropertySignature MatchPropertySignature(byte[] blob)
        {
            if (blob == null)
                throw new ArgumentNullException("blob");

            var index = 0;
            var sig = MatchPropertySignature(blob, ref index);

            Debug.Assert(index == blob.Length);
            return sig;
        }

        public static FieldSignature MatchFieldSignature(byte[] blob)
        {
            if (blob == null)
                throw new ArgumentNullException("blob");

            var index = 0;
            var sig = MatchFieldSignature(blob, ref index);

            Debug.Assert(index == blob.Length);
            return sig;
        }

        public static LocalVariableSignature MatchLocalVariableSignature(byte[] blob)
        {
            if (blob == null)
                throw new ArgumentNullException("blob");

            var index = 0;
            var sig = MatchLocalVariableSignature(blob, ref index);

            Debug.Assert(index == blob.Length);
            return sig;
        }

        public static MethodSpecSignature MatchMethodSpecSignature(byte[] blob)
        {
            if (blob == null)
                throw new ArgumentNullException("blob");

            var index = 0;
            var sig = MatchMethodSpecSignature(blob, ref index);

            Debug.Assert(index == blob.Length);
            return sig;
        }

        public static TypeSpecSignature MatchTypeSpecSignature(byte[] blob)
        {
            if (blob == null)
                throw new ArgumentNullException("blob");

            var index = 0;
            var sig = MatchTypeSpecSignature(blob, ref index);

            Debug.Assert(index == blob.Length);
            return sig;
        }

        private static MethodSignature MatchMethodSignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var sig = new MethodSignature();
            sig.Index = index;

            // CallConv
            sig.CallConv = MatchCallConvSignature(blob, ref index);

            // GenericParameterCount
            if (CallConvUtility.IsGeneric(sig.CallConv.Value))
                sig.GenericParameterCount = MatchCompressedUIntSignature(blob, ref index);

            // ParameterCount
            sig.ParameterCount = MatchCompressedUIntSignature(blob, ref index);

            // ReturnType
            sig.ReturnType = MatchVariableTypeSignature(blob, ref index);

            // ParameterTypes
            // Sentinel
            // ExtraParameterTypes
            if (sig.ParameterCount.Value > 0)
            {
                var allParams = new VariableTypeSignature[sig.ParameterCount.Value];
                var sentinelPos = -1;
                for (var i = 0; i < allParams.Length; i++)
                {
                    if (((ElementType)blob[index]) == ElementType.Sentinel)
                    {
                        sig.Sentinel = MatchElementTypeSignature(blob, ref index);
                        sentinelPos = i--;
                    }
                    else
                    {
                        allParams[i] = MatchVariableTypeSignature(blob, ref index);
                    }
                }

                if (sentinelPos < 0)  // no sentinel present
                {
                    sig.ParameterTypes = new MetadataSignatureList<VariableTypeSignature>(allParams);
                }
                else if (sentinelPos == 0)  // no fixed params
                {
                    sig.ExtraParameterTypes = new MetadataSignatureList<VariableTypeSignature>(allParams);
                }
                else if (sentinelPos >= allParams.Length - 1)
                {
                    // Invalid, sentinel cannot be the last one.
                    throw new InvalidVarArgSentinelException();
                }
                else
                {
                    var nFixedParams = sentinelPos;
                    var nExtraParams = allParams.Length - sentinelPos;

                    var fixedParams = new VariableTypeSignature[nFixedParams];
                    Array.Copy(allParams, 0, fixedParams, 0, nFixedParams);
                    sig.ParameterTypes = new MetadataSignatureList<VariableTypeSignature>(fixedParams);

                    var extraParams = new VariableTypeSignature[nExtraParams];
                    Array.Copy(allParams, sentinelPos, extraParams, 0, nExtraParams);
                    sig.ExtraParameterTypes = new MetadataSignatureList<VariableTypeSignature>(extraParams);
                }
            }  // sig.ParameterCount.Value > 0

            sig.Length = index - sig.Index;
            return sig;
        }

        private static PropertySignature MatchPropertySignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var sig = new PropertySignature();
            sig.Index = index;

            sig.CallConv = MatchCallConvSignature(blob, ref index);
            sig.ParameterCount = MatchCompressedUIntSignature(blob, ref index);
            sig.ReturnType = MatchVariableTypeSignature(blob, ref index);
            sig.Parameters = MatchMetadataSignatureList<VariableTypeSignature>
                (blob, ref index, sig.ParameterCount.Value, MatchVariableTypeSignature);

            sig.Length = index - sig.Index;
            return sig;
        }

        private static FieldSignature MatchFieldSignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var sig = new FieldSignature();
            sig.Index = index;

            sig.CallConv = MatchCallConvSignature(blob, ref index);
            sig.CustomModifiers = MatchCustomModifiers(blob, ref index);
            sig.Type = MatchTypeSignature(blob, ref index);

            sig.Length = index - sig.Index;
            return sig;
        }

        private static LocalVariableSignature MatchLocalVariableSignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var sig = new LocalVariableSignature();
            sig.Index = index;

            sig.CallConv = MatchCallConvSignature(blob, ref index);
            sig.Count = MatchCompressedUIntSignature(blob, ref index);
            sig.Types = MatchMetadataSignatureList<VariableTypeSignature>
                (blob, ref index, sig.Count.Value, MatchVariableTypeSignature);

            sig.Length = index - sig.Index;
            return sig;
        }

        private static MethodSpecSignature MatchMethodSpecSignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var sig = new MethodSpecSignature();
            sig.Index = index;

            sig.CallConv = MatchCallConvSignature(blob, ref index);
            sig.GenericArgumentCount = MatchCompressedUIntSignature(blob, ref index);
            sig.TypeArguments = MatchMetadataSignatureList<TypeSignature>
                (blob, ref index, sig.GenericArgumentCount.Value, MatchTypeSignature);

            sig.Length = index - sig.Index;
            return sig;
        }

        private static TypeSpecSignature MatchTypeSpecSignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var et = (ElementType)blob[index];
            switch (et)
            {
                case ElementType.Ptr: return MatchPointerTypeSpecSignature(blob, ref index);
                case ElementType.FnPtr: return MatchFunctionPointerTypeSpecSignature(blob, ref index);
                case ElementType.Array: return MatchArrayTypeSpecSignature(blob, ref index);
                case ElementType.SzArray: return MatchSzArrayTypeSpecSignature(blob, ref index);
                case ElementType.GenericInst: return MatchGenericInstanceTypeSpecSignature(blob, ref index);
                default:
                    throw new InvalidTypeException();
            }
        }

        private static PointerTypeSpecSignature MatchPointerTypeSpecSignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var sig = new PointerTypeSpecSignature();
            sig.Index = index;

            sig.ElementType = MatchElementTypeSignature(blob, ref index);
            sig.CustomModifiers = MatchCustomModifiers(blob, ref index);
            sig.PointeeType = MatchTypeSignature(blob, ref index);

            sig.Length = index - sig.Index;
            return sig;
        }

        private static FunctionPointerTypeSpecSignature MatchFunctionPointerTypeSpecSignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var sig = new FunctionPointerTypeSpecSignature();
            sig.Index = index;

            sig.ElementType = MatchElementTypeSignature(blob, ref index);
            sig.FunctionSignature = MatchMethodSignature(blob, ref index);

            sig.Length = index - sig.Index;
            return sig;
        }

        private static ArrayTypeSpecSignature MatchArrayTypeSpecSignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var sig = new ArrayTypeSpecSignature();
            sig.Index = index;

            sig.ElementType = MatchElementTypeSignature(blob, ref index);
            sig.ArrayElementType = MatchTypeSignature(blob, ref index);
            sig.ArrayShape = MatchArrayShapeSignature(blob, ref index);

            sig.Length = index - sig.Index;
            return sig;
        }

        private static SzArrayTypeSpecSignature MatchSzArrayTypeSpecSignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var sig = new SzArrayTypeSpecSignature();
            sig.Index = index;

            sig.ElementType = MatchElementTypeSignature(blob, ref index);
            sig.CustomModifiers = MatchCustomModifiers(blob, ref index);
            sig.ArrayElementType = MatchTypeSignature(blob, ref index);

            sig.Length = index - sig.Index;
            return sig;
        }

        private static GenericInstanceTypeSpecSignature MatchGenericInstanceTypeSpecSignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var sig = new GenericInstanceTypeSpecSignature();
            sig.Index = index;

            sig.ElementType = MatchElementTypeSignature(blob, ref index);
            sig.ClassOrValueType = MatchElementTypeSignature(blob, ref index);
            sig.TypeDefOrRef = MatchEncodedTypeDefOrRefSignature(blob, ref index);
            sig.GenericArgumentCount = MatchCompressedUIntSignature(blob, ref index);
            sig.TypeArguments = MatchMetadataSignatureList<TypeSignature>
                (blob, ref index, sig.GenericArgumentCount.Value, MatchTypeSignature);

            sig.Length = index - sig.Index;
            return sig;
        }

        private static TypeSignature MatchTypeSignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var et = (ElementType)blob[index];
            switch (et)
            {
                case ElementType.Boolean:
                case ElementType.Char:
                case ElementType.I1:
                case ElementType.U1:
                case ElementType.I2:
                case ElementType.U2:
                case ElementType.I4:
                case ElementType.U4:
                case ElementType.I8:
                case ElementType.U8:
                case ElementType.R4:
                case ElementType.R8:
                case ElementType.I:
                case ElementType.U:
                case ElementType.String:
                case ElementType.Object:
                case ElementType.Void:  // Note, you should check when don't allow VOID.
                case ElementType.TypedByRef:  // Note, should checked when don't allowed.
                    var sig = new TypeSignature();
                    sig.Index = index;

                    sig.ElementType = MatchElementTypeSignature(blob, ref index);

                    sig.Length = index - sig.Index;
                    return sig;

                case ElementType.Class:
                case ElementType.ValueType:
                    return MatchClassOrValueTypeSignature(blob, ref index);

                case ElementType.MVar:
                case ElementType.Var:
                    return MatchTypeVariableTypeSignature(blob, ref index);

                case ElementType.Ptr: return MatchPointerTypeSpecSignature(blob, ref index);
                case ElementType.FnPtr: return MatchFunctionPointerTypeSpecSignature(blob, ref index);
                case ElementType.Array: return MatchArrayTypeSpecSignature(blob, ref index);
                case ElementType.SzArray: return MatchSzArrayTypeSpecSignature(blob, ref index);
                case ElementType.GenericInst: return MatchGenericInstanceTypeSpecSignature(blob, ref index);

                default:
                    throw new InvalidTypeException();
            }  // switch(et)
        }

        private static ClassOrValueTypeSignature MatchClassOrValueTypeSignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var sig = new ClassOrValueTypeSignature();
            sig.Index = index;

            sig.ElementType = MatchElementTypeSignature(blob, ref index);
            sig.TypeDefOrRef = MatchEncodedTypeDefOrRefSignature(blob, ref index);

            sig.Length = index - sig.Index;
            return sig;
        }

        private static TypeVariableTypeSignature MatchTypeVariableTypeSignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var sig = new TypeVariableTypeSignature();
            sig.Index = index;

            sig.ElementType = MatchElementTypeSignature(blob, ref index);
            sig.Number = MatchCompressedUIntSignature(blob, ref index);

            sig.Length = index - sig.Index;
            return sig;
        }

        private static CallConvSignature MatchCallConvSignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var value = (CallConv)blob[index];
            var sig = new CallConvSignature
            {
                Value = value,
                Index = index,
                Length = 1,
            };

            index += sig.Length;
            return sig;
        }

        private static ElementTypeSignature MatchElementTypeSignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var value = (ElementType)blob[index];
            var sig = new ElementTypeSignature
            {
                Value = value,
                Index = index,
                Length = 1,
            };

            index += sig.Length;
            return sig;
        }

        private static CompressedIntSignature MatchCompressedIntSignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var len = (int)IntUtility.GetCompressedIntSize(blob[index]);
            var buf = new byte[len];
            Array.Copy(blob, index, buf, 0, len);
            var value = IntUtility.DecompressInt(buf);

            var sig = new CompressedIntSignature()
            {
                Value = value,
                Index = index,
                Length = len,
            };

            index += sig.Length;
            return sig;
        }

        private static CompressedUIntSignature MatchCompressedUIntSignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var len = (int)IntUtility.GetCompressedIntSize(blob[index]);
            var buf = new byte[len];
            Array.Copy(blob, index, buf, 0, len);
            var value = IntUtility.DecompressUInt(buf);

            var sig = new CompressedUIntSignature()
            {
                Value = value,
                Index = index,
                Length = len,
            };

            index += sig.Length;
            return sig;
        }

        private static EncodedTypeDefOrRefSignature MatchEncodedTypeDefOrRefSignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var baseSig = MatchCompressedUIntSignature(blob, ref index);
            var sig = new EncodedTypeDefOrRefSignature()
            {
                Value = baseSig.Value,
                Index = baseSig.Index,
                Length = baseSig.Length,
            };

            return sig;
        }

        private static VariableTypeSignature MatchVariableTypeSignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var sig = new VariableTypeSignature();
            sig.Index = index;

            // CustomModifiers
            sig.CustomModifiers = MatchCustomModifiers(blob, ref index);

            // Constraint
            var et = (ElementType)blob[index];
            if (et == ElementType.Pinned)
                sig.Constraint = MatchElementTypeSignature(blob, ref index);

            // ByRef
            et = (ElementType)blob[index];
            if (et == ElementType.ByRef)
                sig.ByRef = MatchElementTypeSignature(blob, ref index);

            // Type
            sig.Type = MatchTypeSignature(blob, ref index);

            sig.Length = index - sig.Index;
            return sig;
        }

        private static CustomModifierSignature MatchCustomModifierSignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var sig = new CustomModifierSignature();
            sig.Index = index;

            sig.ModifierType = MatchElementTypeSignature(blob, ref index);
            sig.TypeDefOrRef = MatchEncodedTypeDefOrRefSignature(blob, ref index);

            sig.Length = index - sig.Index;
            return sig;
        }

        private static ArrayShapeSignature MatchArrayShapeSignature(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var sig = new ArrayShapeSignature();
            sig.Index = index;

            sig.Rank = MatchCompressedUIntSignature(blob, ref index);
            sig.NumberOfSizes = MatchCompressedUIntSignature(blob, ref index);
            if (sig.NumberOfSizes.Value > 0)
                sig.Sizes = MatchMetadataSignatureList<CompressedUIntSignature>
                    (blob, ref index, sig.NumberOfSizes.Value, MatchCompressedUIntSignature);
            sig.NumberOfBounds = MatchCompressedUIntSignature(blob, ref index);
            if (sig.NumberOfBounds.Value > 0)
                sig.Bounds = MatchMetadataSignatureList<CompressedIntSignature>
                    (blob, ref index, sig.NumberOfBounds.Value, MatchCompressedIntSignature);

            sig.Length = index - sig.Index;
            return sig;
        }

        private static MetadataSignatureList<CustomModifierSignature> MatchCustomModifiers(byte[] blob, ref int index)
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            var start = index;
            var cmods = new List<CustomModifierSignature>();

            var et = (ElementType)blob[index];
            while (et == ElementType.CModOpt || et == ElementType.CModReqd)
            {
                var cm = MatchCustomModifierSignature(blob, ref index);
                cmods.Add(cm);

                et = (ElementType)blob[index];
            }

            if (cmods.Count > 0)
                return new MetadataSignatureList<CustomModifierSignature>(cmods.ToArray())
                {
                    Index = start,
                    Length = index - start,
                };
            else
                return null;
        }

        private static MetadataSignatureList<T> MatchMetadataSignatureList<T>
            (byte[] blob, ref int index, uint count, MatchFunction<T> match)
            where T : MetadataSignature
        {
            Debug.Assert(blob != null);
            Debug.Assert(index >= 0);
            Debug.Assert(index < blob.Length);

            if (count < 0)
                throw new ArgumentOutOfRangeException("count");
            else if (count == 0)
                return null;

            var start = index;
            var values = new T[count];
            for (var i = 0; i < values.Length; i++)
                values[i] = match(blob, ref index);

            var sig = new MetadataSignatureList<T>(values)
            {
                Index = start,
                Length = index - start,
            };

            return sig;
        }

        private delegate T MatchFunction<T>(byte[] blob, ref int index)
            where T : MetadataSignature;
    }

    public class MethodSignature : MetadataSignature
    {
        public CallConvSignature CallConv { get; internal set; }
        public CompressedUIntSignature GenericParameterCount { get; internal set; }
        public CompressedUIntSignature ParameterCount { get; internal set; }
        public VariableTypeSignature ReturnType { get; internal set; }
        public MetadataSignatureList<VariableTypeSignature> ParameterTypes { get; internal set; }
        public ElementTypeSignature Sentinel { get; internal set; }
        public MetadataSignatureList<VariableTypeSignature> ExtraParameterTypes { get; internal set; }

        internal MethodSignature() { }
    }

    public class PropertySignature : MetadataSignature
    {
        public CallConvSignature CallConv { get; internal set; }
        public CompressedUIntSignature ParameterCount { get; internal set; }
        public VariableTypeSignature ReturnType { get; internal set; }
        public MetadataSignatureList<VariableTypeSignature> Parameters { get; internal set; }

        internal PropertySignature() { }
    }

    public class FieldSignature : MetadataSignature
    {
        public CallConvSignature CallConv { get; internal set; }
        public MetadataSignatureList<CustomModifierSignature> CustomModifiers { get; internal set; }
        public TypeSignature Type { get; internal set; }

        internal FieldSignature() { }
    }

    public class LocalVariableSignature : MetadataSignature
    {
        public CallConvSignature CallConv { get; internal set; }
        public CompressedUIntSignature Count { get; internal set; }
        public MetadataSignatureList<VariableTypeSignature> Types { get; internal set; }

        internal LocalVariableSignature() { }
    }

    public class MethodSpecSignature : MetadataSignature
    {
        public CallConvSignature CallConv { get; internal set; }
        public CompressedUIntSignature GenericArgumentCount { get; internal set; }
        public MetadataSignatureList<TypeSignature> TypeArguments { get; internal set; }

        internal MethodSpecSignature() { }
    }

    public abstract class TypeSpecSignature : TypeSignature
    {
        internal TypeSpecSignature() { }
    }

    public class PointerTypeSpecSignature : TypeSpecSignature
    {
        public MetadataSignatureList<CustomModifierSignature> CustomModifiers { get; internal set; }
        public TypeSignature PointeeType { get; internal set; }

        internal PointerTypeSpecSignature() { }
    }

    public class FunctionPointerTypeSpecSignature : TypeSpecSignature
    {
        public MethodSignature FunctionSignature { get; internal set; }

        internal FunctionPointerTypeSpecSignature() { }
    }

    public class ArrayTypeSpecSignature : TypeSpecSignature
    {
        public TypeSignature ArrayElementType { get; internal set; }
        public ArrayShapeSignature ArrayShape { get; internal set; }

        internal ArrayTypeSpecSignature() { }
    }

    public class SzArrayTypeSpecSignature : TypeSpecSignature
    {
        public MetadataSignatureList<CustomModifierSignature> CustomModifiers { get; internal set; }
        public TypeSignature ArrayElementType { get; internal set; }

        internal SzArrayTypeSpecSignature() { }
    }

    public class GenericInstanceTypeSpecSignature : TypeSpecSignature
    {
        public ElementTypeSignature ClassOrValueType { get; internal set; }
        public EncodedTypeDefOrRefSignature TypeDefOrRef { get; internal set; }
        public CompressedUIntSignature GenericArgumentCount { get; internal set; }
        public MetadataSignatureList<TypeSignature> TypeArguments { get; internal set; }

        internal GenericInstanceTypeSpecSignature() { }
    }

    public class TypeSignature : MetadataSignature
    {
        public ElementTypeSignature ElementType { get; internal set; }

        internal TypeSignature() { }
    }

    public class ClassOrValueTypeSignature : TypeSignature
    {
        public EncodedTypeDefOrRefSignature TypeDefOrRef { get; internal set; }

        internal ClassOrValueTypeSignature() { }
    }

    public class TypeVariableTypeSignature : TypeSignature
    {
        public CompressedUIntSignature Number { get; internal set; }

        internal TypeVariableTypeSignature() { }
    }

    public class CallConvSignature : MetadataSignature
    {
        public CallConv Value { get; internal set; }

        internal CallConvSignature() { }
    }

    public class ElementTypeSignature : MetadataSignature
    {
        public ElementType Value { get; internal set; }

        internal ElementTypeSignature() { }
    }

    public class CompressedIntSignature : MetadataSignature
    {
        public int Value { get; internal set; }

        internal CompressedIntSignature() { }
    }

    public class CompressedUIntSignature : MetadataSignature
    {
        public uint Value { get; internal set; }

        internal CompressedUIntSignature() { }
    }

    public class EncodedTypeDefOrRefSignature : CompressedUIntSignature
    {
        public TableIndex TableIndex
        {
            get { return CodedTokenIndex.TypeDefOrRef.GetTable(this.Value); }
        }

        public uint RowId
        {
            get { return CodedTokenIndex.TypeDefOrRef.GetRowId(this.Value); }
        }

        internal EncodedTypeDefOrRefSignature() { }
    }

    public class VariableTypeSignature : MetadataSignature
    {
        public MetadataSignatureList<CustomModifierSignature> CustomModifiers { get; internal set; }
        public ElementTypeSignature Constraint { get; internal set; }  // Only applicable in local veriable signature
        public ElementTypeSignature ByRef { get; internal set; }
        public TypeSignature Type { get; internal set; }

        internal VariableTypeSignature() { }
    }

    public class CustomModifierSignature : MethodSignature
    {
        public ElementTypeSignature ModifierType { get; internal set; }
        public EncodedTypeDefOrRefSignature TypeDefOrRef { get; internal set; }

        internal CustomModifierSignature() { }
    }

    public class ArrayShapeSignature : MethodSignature
    {
        public CompressedUIntSignature Rank { get; internal set; }
        public CompressedUIntSignature NumberOfSizes { get; internal set; }
        public MetadataSignatureList<CompressedUIntSignature> Sizes { get; internal set; }
        public CompressedUIntSignature NumberOfBounds { get; internal set; }
        public MetadataSignatureList<CompressedIntSignature> Bounds { get; internal set; }

        internal ArrayShapeSignature() { }
    }

    public class MetadataSignatureList<T> : MetadataSignature, IEnumerable<T>
        where T : MetadataSignature
    {
        public T this[int index]
        {
            get { return _values[index]; }
        }

        public int Count
        {
            get { return _values.Length; }
        }

        public IEnumerator<T> GetEnumerator()
        {
            for (var i = 0; i < _values.Length; i++)
                yield return _values[i];
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        internal MetadataSignatureList(T[] values)
        {
            Debug.Assert(values != null);
            Debug.Assert(values.Length > 0);

            _values = values;
            this.Index = values[0].Index;
            this.Length = values[values.Length - 1].Index
                + values[values.Length - 1].Length;
        }

        private T[] _values;
    }
}
